certbot — Let's Encrypt 证书自动化管理详解

certbot 是 EFF(电子前沿基金会)出品的 ACME 协议客户端,用来从 Let's Encrypt(及其它兼容 ACME 的 CA)免费获取、自动续期并部署 HTTPS/TLS 证书。它是当前互联网上部署 HTTPS 最主流的自动化工具,也是"加密整个互联网"(encrypt the entire Internet)运动的核心组件。

本页先梳理 certbot 的"是什么"与行为/结构,再给出实战操作手册与参数指令速查。

1. 概述

1.1 是什么

certbot 是一个运行在服务器命令行的 Python 工具(certbot 命令)。它帮你完成两件事:

它不是 CA(证书颁发机构)本身,而是连接"你"和"CA"的客户端代理。

1.2 生态:TLS / CA / ACME / Let's Encrypt

要理解 certbot,先分清这几个角色:

专有名词:

1.3 核心心智模型:Lineage(谱系)

certbot 引入 Lineage(证书谱系) 概念:它是一个证书在历次续期中的所有版本 + 该证书的 certbot 配置的集合。每次续期都沿用同一配置,除非显式修改(如增删域名)。

核心心智模型:一个 Lineage = 一个"证书户口"。证书可以反复续期(内容会变),但 Lineage 保持稳定。绝大多数管理操作(续期、吊销、删除、改配置)都通过 --cert-name 指向 Lineage,而不是直接指向某个证书文件。

每个 Lineage 在 /etc/letsencrypt/live/<cert-name>/ 下有一组符号链接,指向 /etc/letsencrypt/archive/<cert-name>/ 里最新的实际文件:

# live/ 下的符号链接(你程序里永远引用这里)
/etc/letsencrypt/live/example.com/cert.pem      → 证书本体
/etc/letsencrypt/live/example.com/chain.pem     → 中间证书链
/etc/letsencrypt/live/example.com/fullchain.pem → 证书+中间链合并
/etc/letsencrypt/live/example.com/privkey.pem   → 私钥(本地生成,永不上传)

# archive/ 下是按续期时间戳归档的真实文件
/etc/letsencrypt/archive/example.com/cert1.pem cert2.pem ...
# renewal/ 下是每个 lineage 的续期配置
/etc/letsencrypt/renewal/example.com.conf

代码演示

# 用 openssl 查看签发的证书
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -text
# 查看 fullchain(程序常用这个)
sudo cat /etc/letsencrypt/live/example.com/fullchain.pem

1.4 安装方式

方式适用特点
Snap(官方推荐)systemd 发行版(Ubuntu/Debian/…)自动预配续期定时器、自动更新,x86_64/ARMv7/ARMv8
Docker容器化环境不能自动安装到 web 服务器,需挂载卷并手动部署证书
Pip虚拟环境官方"尽力而为"支持,建议在 venv 里用
发行版包第三方维护如 apt install certbot,可能滞后于官方版本
注意:certbot 应在目标 web 服务器上以 root 权限运行,而不是在个人电脑上。Docker 方式无法执行 Installer 插件,需要手动把证书放进服务器配置。

2. 行为图(系统"做什么")

2.1 用例图 — 交互全景

管理员通过 certbot 完成签发、续期、吊销、删除、查看、配置等用例;certbot 与 CA(Let's Encrypt)通过 ACME 协议交互,并操作 web 服务器 / DNS。

PlantUML diagram
图 1:certbot 用例图(行为全景)

用例演示

certbot certificates                       # 查看所有证书
certbot certonly -d example.com            # 只签发不安装
certbot --nginx -d example.com             # 签发并自动装进 nginx
certbot renew --dry-run                    # 测试性续期
certbot revoke --cert-name example.com     # 吊销
certbot delete --cert-name example.com     # 删除
专有名词:Cert-Only(仅证书)指只完成认证并保存证书、不安装到服务器的模式,对应 certonly 子命令;Run(运行)指"签发+安装"一体模式,是默认子命令。

2.2 活动图 — certonly 签发主流程

以最常用的 certonly + webroot 认证为例,走一遍完整主流程:解析参数 → 生成密钥对 → 创建 ACME 账号 → 发起订单 → 完成验证 → 获取并保存证书 → 记录续期配置。

PlantUML diagram
图 2:certbot 签发证书主流程活动图(带泳道)

代码演示(真实命令序列)

# 1. 生成密钥对 + 创建/复用 ACME 账号
certbot certonly --webroot -w /var/www/html -d example.com --email you@example.com --agree-tos
# 2. certbot 会临时写入验证文件到 webroot/.well-known/acme-challenge/
# 3. CA 验证通过后,证书落到 live/ 并生成 renewal/ 配置
# 4. 查看结果
certbot certificates

2.3 时序图 — http-01 域名验证

http-01 是最常见的验证方式:certbot 在 webroot 放一个随机内容文件,CA 服务器通过 HTTP 访问 http://域名/.well-known/acme-challenge/<token>,能取到就证明你控制该域名。整个过程走 ACME 协议的 JWS 签名消息。

PlantUML diagram
图 3:http-01 域名验证时序图(ACME 协议)

代码演示(挑战文件长相)

# certbot 在 webroot 下生成的文件(token 目录 + key authorization 内容)
# 请求路径 GET /.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX
# 响应内容形如:HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX.<账号公钥指纹>
# 用 curl 模拟 CA 校验:
curl http://example.com/.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX
专有名词:http-01 挑战——在 HTTP 80 端口放随机文件证明域名控制权;dns-01 挑战——在 DNS 的 _acme-challenge TXT 记录里放随机值,唯一能签发通配符证书(*.example.com)的方式;JWS(JSON Web Signature)——ACME 客户端发给服务器的每条消息都经账号私钥签名。

2.4 状态机图 — Lineage 生命周期

一个 Lineage 从签发到删除的完整生命周期,以及续期/过期/吊销等状态迁移。

PlantUML diagram
图 4:证书 Lineage 生命周期状态机图

代码演示

# 查看状态(VALID 表示还有效,距离过期天数会显示)
certbot certificates
# Found the following certificates:
#   Certificate Name: example.com
#     Domains: example.com, www.example.com
#     Expiry Date: 2026-08-20 12:00:00+00:00 (VALID: 87 days)
#     Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem

3. 结构图(系统"由什么组成")

3.1 部署图 — 生态部署

certbot 部署在你的 web 服务器上,通过 ACME 协议与 Let's Encrypt CA 通信;验证时 web 服务器对外提供 HTTP/DNS 响应;签发后的证书供 nginx/apache 等加载。

PlantUML diagram
图 5:certbot 生态部署图

代码演示(Docker 部署示例)

sudo docker run -it --rm --name certbot \
  -v "/etc/letsencrypt:/etc/letsencrypt" \
  -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
  -p 80:80 \
  certbot/certbot certonly --standalone -d example.com

3.2 组件图 — 内部模块

certbot 源码按职责拆分为多个包:acme(协议层)、certbot(主客户端)、插件包(apache/nginx/dns-*),通过插件接口解耦。

PlantUML diagram
图 6:certbot 内部组件图

代码演示(列出已安装插件)

certbot plugins
# 会列出 certbot.plugins 入口点注册的认证器与安装器
专有名词:entry point(入口点)——Python 包通过 importlib.metadata 把插件注册到 certbot.plugins 组,certbot 启动时动态发现插件,因此第三方可以写插件而不改主程序。

3.3 包图 — /etc/letsencrypt 目录布局

证书、私钥、续期配置、账号、钩子脚本的存放结构。所有文件均为 PEM 编码;私钥只在你本地,从不上传 CA。

PlantUML diagram
图 7:/etc/letsencrypt 目录布局包图

代码演示(查看目录树)

sudo tree /etc/letsencrypt
# ├── live/example.com/{cert,chain,fullchain,privkey}.pem   # 符号链接
# ├── archive/example.com/{cert1,chain1,fullchain1,privkey1}.pem
# ├── renewal/example.com.conf
# ├── renewal-hooks/{pre,deploy,post}/                      # 钩子脚本目录
# ├── accounts/acme-v02.api.letsencrypt.org/directory/<acct>/
# └── cli.ini                                               # 全局配置(可选)
专有名词:fullchain 是服务器实际加载的证书(叶子证书 + 中间链合并);privkey(私钥)用于 TLS 握手,必须严格保密(权限 0600);accounts/ 存放 ACME 账号私钥与注册信息。

3.4 类图 — 插件架构

certbot 通过两个核心接口扩展能力:Authenticator(完成挑战证明域名控制权)与 Installer(把证书配置进服务器)。内置的 Apache/nginx 插件同时实现两者;standalone 只做认证;DNS 插件只做 dns-01 认证。

PlantUML diagram
图 8:certbot 插件架构类图

代码演示(组合认证器+安装器)

# 用 webroot 做认证、apache 做安装的混合模式
certbot run -a webroot -i apache -w /var/www/html -d example.com
# 认证器必填;安装器只在要装进服务器时需要
专有名词:Authenticator 接口的关键方法:get_chall_pref()(声明支持的挑战类型优先级)、perform(achalls)(完成挑战)、cleanup(achalls)(验证后清理临时文件/记录);Installer 接口负责修改服务器配置加载证书,并可通过 supported_enhancements() 声明额外安全增强(如 HSTS、HTTP→HTTPS 跳转)。

4. 实战操作手册

4.1 安装

Snap(推荐,Ubuntu/Debian):

sudo apt update && sudo apt install snapd
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot   # 放入 PATH

Docker:

docker run -it --rm --name certbot \
  -v "/etc/letsencrypt:/etc/letsencrypt" \
  -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
  -p 80:80 certbot/certbot certonly --standalone -d example.com

Pip(虚拟环境):

python3 -m venv certbot-venv && source certbot-venv/bin/activate
pip install certbot
certbot --version

4.2 签发证书(各插件场景)

场景 A:nginx 服务器,自动安装

# 自动检测 nginx 配置、签发并安装、开启自动跳转 HTTPS
sudo certbot --nginx -d example.com -d www.example.com --email you@example.com --agree-tos

场景 B:Apache 服务器,自动安装

sudo certbot --apache -d example.com

场景 C:有 web 服务器但不装到服务器(webroot)

# webroot 是网站根目录,不改服务器配置,只临时写验证文件
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
# 多域名不同 webroot:每个 -w 作用于其后的 -d
sudo certbot certonly --webroot -w /var/www/example -d example.com -w /var/www/other -d other.example.net

场景 D:无 web 服务器(standalone,需空闲 80 端口)

sudo certbot certonly --standalone -d example.com
# 若需 IPv6/IPv4 指定监听地址
sudo certbot certonly --standalone --http-01-address 0.0.0.0 -d example.com

场景 E:通配符证书(dns-01,唯一方式)

# 以 cloudflare 为例(需先装插件 + API 凭据)
pip install certbot-dns-cloudflare
sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /root/.cloudflare.ini \
  -d example.com -d '*.example.com'
# 插件通过 DNS API 自动加/删 _acme-challenge 的 TXT 记录

场景 F:完全手动(manual,适合无法自动化的环境)

# 交互式提示你手动放置文件或 TXT 记录;不支持自动续期(除非配 auth hook)
sudo certbot certonly --manual -d example.com
sudo certbot certonly --manual --preferred-challenges dns -d example.com
注意:--manual 签发的证书不支持自动续期,除非加 --manual-auth-hook 脚本自动化验证步骤。能用其他插件就别用 manual。

4.3 证书管理

查看

certbot certificates                                # 列出所有 lineage
certbot certificates --cert-name example.com        # 只看某一个

更新域名集合

# 用 --cert-name 精确修改某 lineage 的域名(可增可删)
sudo certbot certonly --cert-name example.com -d example.com              # 移除 www
sudo certbot certonly --cert-name example.com -d example.org -d www.example.org  # 整体替换
# 或者用 --expand 只增不减
sudo certbot --expand -d example.com -d newdomain.com

强制更新 / 重复签发

sudo certbot certonly --force-renewal -d example.com   # 同域名重新签发,更新 live 软链
sudo certbot certonly --duplicate -d example.com       # 生成一个完全独立的证书

切换密钥类型(RSA ↔ ECDSA)

# 默认已是 ECDSA P-256;RSA 2048/4096 可指定
sudo certbot renew --key-type ecdsa --cert-name example.com --force-renewal
# 全局默认写进 cli.ini:key-type = ecdsa / rsa-key-size = 4096

吊销

sudo certbot revoke --cert-name example.com
sudo certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem
sudo certbot revoke --cert-path ... --key-path .../privkey.pem   # 用证书私钥吊销
sudo certbot revoke --cert-name example.com --reason keycompromise  # 注明原因

删除(不可逆,务必先确认服务器不再引用)

sudo grep -R live/example.com /etc/nginx /etc/httpd /etc/apache2   # 先查引用
sudo certbot delete --cert-name example.com                        # 再删

重命名

# certbot 无 rename 命令;用 --cert-name 重新签发并删旧的
sudo certbot certonly --cert-name newname -d example.com -d www.example.com
sudo certbot install --cert-name newname   # 更新服务器配置里的引用
sudo certbot delete --cert-name oldname    # 删除旧 lineage

4.4 自动续期与 Hooks

certbot 4.0 起,证书在剩余有效期小于 1/3 时判定"该续期了"(有效期≤10 天的证书阈值放宽到 1/2)。certbot renew 会检查所有 lineage,只续临近过期的,因此可以频繁安全地执行。

检查自动续期是否已配好

sudo certbot renew --dry-run                # 干跑一次(用 staging 服务器,不写盘)
systemctl list-timers | grep certbot        # 看 systemd timer
grep certbot /etc/crontab                   # 或看 cron

手工加 cron(macOS/Linux 通用)

# 每天 0 点和 12 点随机延时后执行,-q 静默
SLEEPTIME=$(awk 'BEGIN{srand(); print int(rand()*(3600+1))}')
echo "0 0,12 * * * root sleep $SLEEPTIME && certbot renew -q" | sudo tee -a /etc/crontab

Hooks(续期前后/部署后执行)

# 命令行 hook:standalone 场景需停/起 web 服务器
sudo certbot renew --pre-hook "service nginx stop" --post-hook "service nginx start"
# 只在成功续期后执行部署动作
sudo certbot renew --deploy-hook "/path/to/script"
# 目录式 hook(所有子命令都生效,按文件名顺序执行)
/etc/letsencrypt/renewal-hooks/pre/     # 续期前
/etc/letsencrypt/renewal-hooks/deploy/  # 续期成功后
/etc/letsencrypt/renewal-hooks/post/    # 续期后
# 示例:重启 haproxy
sudo sh -c 'printf "#!/bin/sh\nservice haproxy restart\n" > /etc/letsencrypt/renewal-hooks/post/haproxy.sh'
sudo chmod 755 /etc/letsencrypt/renewal-hooks/post/haproxy.sh
技巧:post-hook 里可用环境变量 RENEWED_DOMAINS 与 FAILED_DOMAINS(空格分隔的域名列表)判断续期结果,决定后续动作。

修改某证书的续期配置(v2.3+ 推荐 reconfigure)

# 会先用 staging 服务器做测试续期,成功才保存
sudo certbot reconfigure --cert-name example.com --webroot-path /path/to/new/location
# 老版本流程:dry-run → 带 --force-renewal 正式续期以持久化

5. 参数指令速查手册

5.1 子命令一览

分类子命令作用
签发与安装run(默认)签发并安装到当前 web 服务器
certonly只签发/续期,不安装
renew续期所有临近过期的证书(适合自动化)
enhance为现有配置添加安全增强(如 HSTS)
证书管理certificates显示所有证书信息
revoke吊销证书(--cert-name 或 --cert-path)
delete删除证书(--cert-name)
reconfigure更新证书续期配置(--cert-name)
账号管理register创建 ACME 账号
unregister注销 ACME 账号
update_account更新账号信息
show_account查看账号详情
其他plugins / install / help列插件 / 安装证书到服务器 / 帮助

5.2 常用参数

参数说明
-d, --domains证书包含的域名,可多次指定或用逗号分隔
--cert-name指定 Lineage 名称(管理操作的关键)
-a, --authenticator / -i, --installer指定认证器 / 安装器插件
--apache / --nginx / --webroot / --standalone / --manual常用插件的快捷开关
-w, --webroot-pathwebroot 插件:web 根目录路径
--preferred-challenges首选挑战类型(http / dns)
--email / -m账号通知邮箱(证书将过期时 CA 会发信)
--agree-tos同意 ACME 订阅协议(非交互必需)
-n, --non-interactive非交互运行(cron 里必用)
--force-renewal忽略到期时间强制续期(勿频繁使用,撞限速)
--duplicate签发一个与现有完全独立的证书
--expand在现有证书上新增域名
--allow-subset-of-names部分域名验证成功也继续签发
--key-typersa / ecdsa(默认 ecdsa)
--rsa-key-size / --elliptic-curveRSA 位数(默认 2048)/ ECDSA 曲线(默认 secp256r1)
--dry-run测试性运行(默认用 staging 服务器,不落盘)
--test-cert / --staging用 staging 服务器获取测试证书(无速率限制)
--server指定 ACME 目录 URL(可切其他 CA,如 Sectigo)
--eab-kid / --eab-hmac-key外部账号绑定(EAB)凭据,部分 CA 需要
--pre-hook / --post-hook / --deploy-hook续期前后 / 成功部署后执行的命令
--preferred-challenges首选挑战类型
-c, --config指定配置文件(默认 /etc/letsencrypt/cli.ini)
--config-dir / --work-dir / --logs-dir覆盖默认目录(/etc/letsencrypt / /var/lib/letsencrypt / /var/log/letsencrypt)
--max-log-backups日志保留份数(默认 1000,0 关闭轮转)
-v / --verbose日志详细程度,可 -vvv
--no-directory-hooks不自动运行 renewal-hooks 目录下的脚本
--reason吊销原因(keycompromise / superseded 等)
学习建议:任何时候可 certbot --help all 查看全部参数;certbot --help renew 查看续期相关 hook 细节。

5.3 配置文件 cli.ini / renewal

/etc/letsencrypt/cli.ini 是全局配置,作用于所有调用。默认读取 /etc/letsencrypt/cli.ini 与 ~/.config/letsencrypt/cli.ini。示例:

# cli.ini 示例
key-type = ecdsa
elliptic-curve = secp384r1
rsa-key-size = 4096
email = you@example.com
# authenticator = standalone
# authenticator = webroot
# webroot-path = /usr/share/nginx/html
# agree-tos = true
# server = https://acme-v02.api.letsencrypt.org/directory
注意:全局 cli.ini 里不要写域名(会破坏续期);renewal/<name>.conf 是证书级续期配置,不建议手工修改,要改请用 certbot reconfigure。
# renewal/example.com.conf 结构(供理解,勿手改)
version = 4.2.0
archive_dir = /etc/letsencrypt/archive/example.com
cert = /etc/letsencrypt/live/example.com/cert.pem
privkey = /etc/letsencrypt/live/example.com/privkey.pem
chain = /etc/letsencrypt/live/example.com/chain.pem
fullchain = /etc/letsencrypt/live/example.com/fullchain.pem
[renewalparams]
account = <账号id>
server = https://acme-v02.api.letsencrypt.org/directory
authenticator = webroot
key_type = ecdsa
post_hook = "echo 'renewed'"

6. 故障排查与常见问题

症状原因 / 排查
http-01 验证失败:Connection refused / 40480 端口未开放或 .well-known 被服务器特殊处理;curl http://域名/.well-known/... 自测
dns-01 验证失败:TXT 记录查不到DNS 传播延迟;用 dig _acme-challenge.example.com TXT 检查;确认插件凭据有写权限
too many certificates / too many requests撞 Let's Encrypt 速率限制(每周每注册域名 5 张证书);用 --staging 测试、少用 --force-renewal
renew 总是失败 / exit code 1看 /var/log/letsencrypt/letsencrypt.log;手动 certbot renew --cert-name X --dry-run -v
证书明明新签仍显示要续期--dry-run 不会更新续期记录;真正持久化需一次真实续期
renew 提示 unauthorized(无权限)证书是用不同插件/账号签的;重跑原签发命令相同参数
Standalone 端口被占先停 web 服务器或用 webroot 插件;ss -ltnp | grep :80
客户端不信任 ACME 服务器证书设置 REQUESTS_CA_BUNDLE 环境变量指向你的 CA bundle(certbot 用 requests,不读系统根证书库)
证书未自动续期检查 snap timer / cron 是否存在;certbot renew --dry-run 验证
多实例并发报锁冲突默认目录有锁文件;并行实例需用不同 --config-dir/--work-dir/--logs-dir
日志:默认日志在 /var/log/letsencrypt/letsencrypt.log;加 -vvv 能看到 ACME 协议的完整请求/响应。

7. 参考资料